#include "simulador.h"

namespace SimuladorAD
{
    Simulador::Simulador(Configuracao conf) {

        m_config = conf;

        // Instancia os geradores de chegada e serviço, passando os parâmetros escolhidos.
        m_gerador_chegadas = new Exponencial(m_config.taxa_chegada, m_config.semente_chegada, conf.deterministico);
        m_gerador_servico = new Exponencial(m_config.taxa_servico, m_config.semente_servico, conf.deterministico);

		inicializa();
    }


    void Simulador::inicializa()
    {

		//Limpa estruturas do simulador
		m_fila1.clear();
		m_fila2.clear();

		resultados.clear();

        m_tempo_atual = 0.0;

        //Evento inicial do simulador.
		m_proxima_chegada = m_gerador_chegadas->gera_amostra();
		m_termino_servico = std::numeric_limits<double>::infinity();

		m_prox_id = 0;

        m_fregueses_servidos = 0;

        m_servidor_ocupado = false;
     }


    void Simulador::reinicia_simulador()
    {
        inicializa();
    }


    void Simulador::executa()
    {
		//Executa o simulador no modo Batch ou Replicativo
		if (m_config.modo == ModoBatch) {
			rodar_batch();
        }
        else {
            rodar_replicativo();
        }
    }

    void Simulador::rodar_batch() {

        ResultadosSimulacao res; // Acumulador dos resultados.

        inicializa();

        //Executando fase transiente
        rodada(false, 0 , m_config.tam_fase_transiente);

        //Executando as rodadas
        for (int cor = 1; cor <= m_config.num_rodadas; cor++) {

			 //Executando a rodada de cor 'cor'
            res += rodada(true, cor, m_config.tam_rodada);
            //escrever_estatisticas(res, cor); // Escreve as estimativas em buffer.
        }

        res.calculaEstatisticas();
        escrever_resultados(res); // Escreve os resultados em buffer.
    }



    void Simulador::rodar_replicativo() {

        ResultadosSimulacao res; // Acumulador dos resultados.

        for (int i = 0; i < m_config.num_rodadas; i++) {

            inicializa();

            //Executando fase transiente - cor 0
            rodada(false, 0 , m_config.tam_fase_transiente);

            //Executando a rodada de cor 1
            res += rodada(true, 1 , m_config.tam_rodada);
        }

        res.calculaEstatisticas();
        escrever_resultados(res); // Escreve os resultados em buffer.
    }

    ResultadosSimulacao Simulador::rodada(bool coleta, int cor, int tam_rodada) {

        m_fregueses_servidos = 0;
        resultados.clear();

        while(m_fregueses_servidos < tam_rodada)
        {
            if(m_proxima_chegada <= m_termino_servico) { //Chegou um novo cliente ao sistema.
				trata_chegada_no_sistema(cor);
            }
            else { //O cliente em serviço terminou.
				trata_termino_servico(coleta, cor);
            }

            //Se o servidor estiver vazio, verifica se tem algum cliente para ser atendido.
            if(m_servidor_ocupado == false) {
                coloca_em_servico();
            }
        }

        if(coleta == true) {
            resultados.calculaEstatisticas();
        }

        return resultados;
    }


    void Simulador::coloca_em_servico(void){

		if(!m_fila1.empty()) //Se tiver alguem na fila 1, de maior prioridade para ser atendido.
		{
			//Move da fila 1 para o servidor.
			m_fregues_em_servico = m_fila1.front();
			m_fila1.pop_front();

			m_fregues_em_servico.entra_servico_1(m_tempo_atual);
			m_servidor_ocupado = true;

			//Agenda o evento de conclusão do servico
			m_termino_servico = m_tempo_atual + m_gerador_servico->gera_amostra();

			if (m_config.verboso == true)
			{
				*(saida_std->buffer_msg) << "-- Tempo evento: " << m_tempo_atual << " [Evento] Fregues de id  " << m_fregues_em_servico.id() << " da fila1 entra no servidor." << std::endl;
				// deixar isso aqui
				//std::cout << "-- Tempo evento: " << m_tempo_atual << " [Evento] Fregues de id  " << m_fregues_em_servico.id() << " da fila1 entra no servidor." << std::endl;

			}

		}
		else if(!m_fila2.empty()) //Se a fila 1 estiver vazia, verificamos a fila 2...
		{

			//Move da fila 2 para o servidor.
			m_fregues_em_servico = m_fila2.front();
			m_fila2.pop_front();

			if(m_fregues_em_servico.tempo_servico_2() < 0){ //Se for a primeira vez que o fregues entra no servico 2
				m_fregues_em_servico.entra_servico_2(m_gerador_servico->gera_amostra(), m_tempo_atual);
			}else{
				m_fregues_em_servico.entra_servico_2(0, m_tempo_atual);
			}

			m_servidor_ocupado = true;

			//Agenda o evento de conclusão do servico
			m_termino_servico = m_tempo_atual + m_fregues_em_servico.servico_2_residual();

			if (m_config.verboso == true)
			{
				*(saida_std->buffer_msg) << "-- Tempo evento: " << m_tempo_atual << " [Evento] Fregues de id  " << m_fregues_em_servico.id() << " da fila2 entra no servidor." << std::endl;
				// deixar isso aqui
				//std::cout << "-- Tempo evento: " << m_tempo_atual << " [Evento] Fregues de id  " << m_fregues_em_servico.id() << " da fila2 entra no servidor." << std::endl;

			}
		}
    }

    void Simulador::trata_termino_servico(bool coleta, int rodada){

		m_tempo_atual = m_termino_servico;

		if(m_fregues_em_servico.fila_atual() == 1) //Se o fregues veio da Fila1, envia ele para a fila 2.
		{
			m_fregues_em_servico.entra_fila_2(m_tempo_atual); // armazena o tempo em que entrou na fila 2

			m_fila2.push_back(m_fregues_em_servico); //insere o fregues na fila 2

			if (m_config.verboso == true)
			{
				*(saida_std->buffer_msg) << "-- Tempo evento: " << m_tempo_atual <<  " [Evento] Fregues de id  " << m_fregues_em_servico.id() << " da fila1 terminou o servico no servidor." << std::endl;
				// deixar isso aqui
				//std::cout << "-- Tempo evento: " << m_tempo_atual <<  " [Evento] Fregues de id  " << m_fregues_em_servico.id() << " da fila1 terminou o servico no servidor." << std::endl;

			}


		} else if(m_fregues_em_servico.fila_atual() == 2) //Se o fregues veio da Fila2, coleta todos os dados e remove do sistema.
		{

			if (m_config.verboso == true)
			{
				*(saida_std->buffer_msg) << "-- Tempo evento: " << m_tempo_atual << " [Evento] Fregues de id " << m_fregues_em_servico.id() << " da fila2 terminou o servico no servidor." << std::endl;
				// deixar isso aqui
				//std::cout << "-- Tempo evento: " << m_tempo_atual << " [Evento] Fregues de id " << m_fregues_em_servico.id() << " da fila2 terminou o servico no servidor." << std::endl;

			}

			m_fregues_em_servico.sai_sistema(m_tempo_atual); // armazena o tempo em que saiu do sistema

			if (m_fregues_em_servico.cor() == rodada)
			{
				if(coleta) //Devemos coletar os dados do cliente antes de remove-lo do sistema.
				{
					resultados.quantidade++;

					//Dados da fila 1
					resultados.fila[0].insere_amostra(m_fregues_em_servico.tempo_total_fila1(),
									m_fregues_em_servico.total_pessoas_fila1(),
									m_fregues_em_servico.pessoas_espera_fila1(),
									m_fregues_em_servico.tempo_espera_fila1());

					//Dados da fila 2
					resultados.fila[1].insere_amostra(m_fregues_em_servico.tempo_total_fila2(),
									m_fregues_em_servico.total_pessoas_fila2(),
									m_fregues_em_servico.pessoas_espera_fila2(),
									m_fregues_em_servico.tempo_espera_fila2());

				}

                m_fregueses_servidos++;
			}

		}

		m_servidor_ocupado = false;
		m_termino_servico = std::numeric_limits<double>::infinity();
    }

    void Simulador::trata_chegada_no_sistema(int cor){

		//Cria um novo Fregues e adiciona na fila 1
		Fregues novoFregues = Fregues(m_prox_id++, cor, m_proxima_chegada, m_fila1.size(), m_fila2.size(),
		(m_servidor_ocupado == false) ? 0 : m_fregues_em_servico.fila_atual());

		//Interrompe o servico da classe 2 casa tenha alguém em servico
		if(m_servidor_ocupado == true && m_fregues_em_servico.fila_atual() == 2){

			m_fregues_em_servico.volta_fila_2(m_proxima_chegada - m_tempo_atual, m_proxima_chegada);

			m_servidor_ocupado = false; // servidor fica vazio pois o freguês da fila 2 sofreu preeempção

			m_termino_servico = std::numeric_limits<double>::infinity(); //tempo do próximo término de serviço = INF , ou seja, não tem ninguém no servidor.

			m_fila2.push_front(m_fregues_em_servico);//freguês do tipo 2 que sofreu preeempção retorna a fila 2;
		}

		m_tempo_atual = m_proxima_chegada;

		m_fila1.push_back(novoFregues);  //insere o freguês que chegou na fila 1

		// Impressoes do modo verboso
		if (m_config.verboso == true)
		{
            *(saida_std->buffer_msg) << "-- Tempo evento: " << m_tempo_atual << " [Evento] Fregues de id " << novoFregues.id() << " chega no sistema. " << std::endl;
			// deixar isso aqui
			//std::cout << "-- Tempo evento: " << m_tempo_atual << " [Evento] Fregues de id " << novoFregues.id() << " chega no sistema. " << std::endl;

		}

		//Programa a próxima chegada
		m_proxima_chegada += m_gerador_chegadas->gera_amostra();
    }

    void Simulador::escrever_estatisticas(ResultadosSimulacao res, int rodada) {

        res.calculaEstatisticas();

        for(int i = 0; i < 2; ++i) {
            saida_graf->escrever_estimativa("N", i, res.fila[i].N, rodada,
                                            res.fila[i].var_N, int_conf(res.fila[i].var_N, res.quantidade));
            saida_graf->escrever_estimativa("Nq", i, res.fila[i].Nq, rodada,
                                            res.fila[i].var_Nq, int_conf(res.fila[i].var_Nq, res.quantidade));
            saida_graf->escrever_estimativa("W", i, res.fila[i].W, rodada,
                                            res.fila[i].var_W, int_conf(res.fila[i].var_W, res.quantidade));
            saida_graf->escrever_estimativa("vW", i, res.fila[i].vW, rodada,
                                            res.fila[i].var_vW, int_conf(res.fila[i].var_vW, res.quantidade));
            saida_graf->escrever_estimativa("T", i, res.fila[i].T, rodada,
                                            res.fila[i].var_T, int_conf(res.fila[i].var_T, res.quantidade));

        }

    }

    void Simulador::escrever_resultados(ResultadosSimulacao res) {
        for (int i = 0; i < 2; i++) {
            *(saida_std->buffer_saida) << "N"  << i+1 << "  " << res.fila[i].N  << " ";
            *(saida_std->buffer_saida) << "Ic " << int_conf(res.fila[i].var_N, res.quantidade) << std::endl;

            *(saida_std->buffer_saida) << "Nq" << i+1 << " " <<  res.fila[i].Nq << " ";
            *(saida_std->buffer_saida) << "Ic " << int_conf(res.fila[i].var_Nq, res.quantidade) << std::endl;

            *(saida_std->buffer_saida) << "W"  << i+1 <<  "  " << res.fila[i].W  << " ";
            *(saida_std->buffer_saida) << "Ic " << int_conf(res.fila[i].var_W, res.quantidade) << std::endl;

            *(saida_std->buffer_saida) << "vW" << i+1 <<  " " << res.fila[i].vW << " ";
            *(saida_std->buffer_saida) << "Ic " << int_conf(res.fila[i].var_vW, res.quantidade) << std::endl;

            *(saida_std->buffer_saida) << "T"  << i+1 <<  "  " << res.fila[i].T  << " ";
            *(saida_std->buffer_saida) << "Ic " << int_conf(res.fila[i].var_T, res.quantidade) << std::endl;

            *(saida_std->buffer_saida) << std::endl << std::endl;
        }

        *(saida_std->buffer_msg) << "Modo da simulação: " << m_config.modo << std::endl;
        *(saida_std->buffer_msg) << "Número de rodadas: " << m_config.num_rodadas << std::endl;
        *(saida_std->buffer_msg) << "Tamanho da rodada: " << m_config.tam_rodada << std::endl;
        *(saida_std->buffer_msg) << "Tamanho da fase transiente: " << m_config.tam_fase_transiente << std::endl;
        *(saida_std->buffer_msg) << "Taxa de chegada (lambda): " << m_config.taxa_chegada << std::endl;
        *(saida_std->buffer_msg) << "Taxa de serviço: " << m_config.taxa_servico << std::endl;
        *(saida_std->buffer_msg) << "Semente de chegada: " << m_config.semente_chegada << std::endl;
        *(saida_std->buffer_msg) << "Semente de serviço: " << m_config.semente_servico << std::endl;
        *(saida_std->buffer_msg) << "Modo deterministico: " << m_config.deterministico << std::endl;

        // resultados (buffer_saida): escrever campos consolidados res.* (formatados)
        // mensagens (buffer_msg): saída verbosa e parâmetros usados: semente, lambda, ...
    }

    long int Simulador::semente_gerador_chegadas(void)
    {
        return m_gerador_chegadas->semente();
    }

    long int Simulador::semente_gerador_servico(void)
    {
        return m_gerador_servico->semente();

    }

	void Simulador::geradores_deterministicos(bool ativado){

		//ativa/desativa o modo determinístico de ambos os geradores

		m_gerador_chegadas->deterministico(ativado);
		m_gerador_servico->deterministico(ativado);
	}

	void Simulador::verboso(bool ativado){
		m_config.verboso = ativado;
	}
}
